package com.tms.ned.controlmonitor.adapters;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import javax.swing.Timer;

import com.tms.ned.controlmonitor.ComponentChangeEvent;
import com.tms.ned.controlmonitor.ComponentChangeListener;
import com.tms.ned.controlmonitor.decorators.ComponentDecorator;
import com.tms.ned.controlmonitor.validate.ValidationStatus;
import com.tms.ned.controlmonitor.validate.Validator;

/*
 * Licensed under the Apache License, Version 2.0;<br> 
 * 
 * You may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless 
 * required by applicable law or agreed to in writing, software distributed under the License 
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and limitations under the License. 
 */


/**
 * A ComponentAdapter that wraps another ComponentAdapter, but queues up the
 * from the wrapped adapter and fires them all off in order, after a specified
 * delay.
 *
 * Essentially, this adapter delays the ComponentChangeEvent firing for a
 * specified delay.
 *
 * This is very useful for listening to components whose event handlers take a
 * long time to compute (or hit a remote server for some validation, for
 * example).
 *
 * This class was designed to be used as a drop-in wrapping component adapter
 * for use with ControlMonitor, but it can be used stand-alone as well.
 *
 * DelayingComponentAdapter was inspired by code from the JGoodies "Validation"
 * library by Karsten Lentzsch. He's a much better programmer than I am, but I
 * think that the use case for this class is more flexible. (This class delays
 * ALL component events from the adapted component, JGoodies uses a delay just
 * for handling revalidation.)
 *
 *
 * @author bshannon
 *
 */
public class DelayingComponentAdapter implements ComponentAdapter, ActionListener {

	/**
	 * The Swing timer used to fire events at a certain interval.
	 */
	private Timer timer;

	/**
	 * The ComponentAdapter to which we delegate most method calls.
	 */
	private AbstractComponentAdapter delegate;

	/**
	 * A set of events that have been collected from the delegate adapter that
	 * need to be fired when the timer's delay is up.
	 */
	private Set<ComponentChangeEvent> collectedEvents = new LinkedHashSet<ComponentChangeEvent>(0);

	/**
	 * This listener is interposed between the actual listeners and the delegate
	 * in order to reset the time and collect events from the delegate; to be
	 * re-fired later.
	 */
	private ComponentChangeListener internalListener = new ComponentChangeListener() {
		public void componentChanged(ComponentChangeEvent event) {
			resetTimer();

			// possibly save memory by only storing the most recent event
			// if fireOnlyMostRecentEvent flag is set.
			if(fireOnlyMostRecentEvent) {
				collectedEvents.clear();
			}
			collectedEvents.add(event);
		}
	};

	/**
	 * Normal ComponentAdapter classes have listeners added to them (usually a
	 * ControlMonitor during a registerComponent() call) This list will hold any
	 * (but usually only one) actual listeners. (We'll re-fire events to them
	 * later.)
	 */
	private List<ComponentChangeListener> actualChangeListeners = new ArrayList<ComponentChangeListener>(0);

	/**
	 * If this property is true, then only the latest event on the underlying
	 * delegated ComponentAdapter is re-fired after the delay.
	 */
	private boolean fireOnlyMostRecentEvent;

	/**
	 * Constructs a DelayingComponentAdapter, using the given <i>delegate</i>,
	 * and re-fires the event after the specified <i>delay</i>
	 *
	 * @param delegate
	 *            The underlying ComponentAdapter.
	 * @param delay
	 *            The number of milliseconds to delay before re-firing events.
	 */
	public DelayingComponentAdapter(AbstractComponentAdapter delegate, int delay) {
		this(delegate, delay, true);
	}


	/**
	 * Constructs a DelayingComponentAdapter, using the given <i>delegate</i>,
	 * and re-fires the event after the specified <i>delay</i>
	 *
	 * @param delegate
	 *            The underlying ComponentAdapter.
	 * @param delay
	 *            The number of milliseconds to delay before re-firing events.
	 * @param fireOnlyMostRecentEvent
	 *            TRUE if this class only needs to fire the most recent event.
	 *            FALSE if all events that happened during the delay need to
	 *            fire.
	 */
	public DelayingComponentAdapter(AbstractComponentAdapter delegate, int delay, boolean fireOnlyMostRecentEvent) {
		this.delegate = delegate;
		this.fireOnlyMostRecentEvent = fireOnlyMostRecentEvent;
		timer = new Timer(delay, this);
		delegate.addChangeListener(internalListener);
		timer.start();

	}

	private void resetTimer() {
		timer.restart();
	}

	/**
	 * This is the callback from the timer. Loops through all the events, and
	 * "fires" them for all component change listeners when the delay is up.
	 * Clears the list of queued events when all the firing is done.
	 */
	public void actionPerformed(ActionEvent e) {
		System.out.println("Timer fired: " + collectedEvents.size() + " events queued up...");
		for (ComponentChangeEvent evt : collectedEvents) {
			for (ComponentChangeListener listener : actualChangeListeners) {
				listener.componentChanged(evt);
			}
		}
		collectedEvents.clear();
	}

	/**
	 * This is where the listener interception happens. Rather than adding the
	 * change listener to the delegate, we'll keep a list of all the "actual"
	 * change listeners and add the listener to that list, in order to refire
	 * the events when the timer expires. This method doesn't delegate to the
	 * delegate ComponentAdapter.
	 */
	public void addChangeListener(ComponentChangeListener c) {
		actualChangeListeners.add(c);
	}

	/**
	 * Changes the delay (expressed in milliseconds) between calling of events.
	 *
	 * @param millisBetweenEvents
	 *            integer (milliseconds) between event firings.
	 */
	public void setDelay(int millisBetweenEvents) {
		timer.stop();
		timer.setDelay(millisBetweenEvents);
		timer.start();
	}

	/**
	 * Simple delegation after this point.
	 */

	public Component getComponent() {
		return delegate.getComponent();
	}

	public ComponentDecorator getComponentDecorator() {
		return delegate.getComponentDecorator();
	}

	public ValidationStatus getCurrentValidationStatus() {
		return delegate.getCurrentValidationStatus();
	}

	public Object getCurrentValue() {
		return delegate.getCurrentValue();
	}

	public Object getInitialValue() {
		return delegate.getInitialValue();
	}

	public String getName() {
		return delegate.getName();
	}

	public boolean isDirty() {
		return delegate.isDirty();
	}

	public boolean isValid() {
		return delegate.isValid();
	}

	public void removeChangeListener(ComponentChangeListener c) {
		delegate.removeChangeListener(c);
	}

	public void setInitialValue(Object o) {
		delegate.setInitialValue(o);
	}

	public void setValidator(Validator validator) {
		delegate.setValidator(validator);
	}

	public void validate() {
		delegate.validate();
	}

}
